﻿using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;

namespace SQLPlugin
{
    /// <summary>
    /// SQL highlighter
    /// </summary>
    public class SQLPlugin : Interface.IPlugin
    {
        private string Keywords = 
                                      "absolute action ada add admin after aggregate "
                                      + "alias all allocate alter and any are array as asc "
                                      + "assertion at authorization avg backup before begin "
                                      + "between binary bit bit_length blob boolean both breadth "
                                      + "break browse bulk by call cascade cascaded case cast "
                                      + "catalog char char_length character character_length "
                                      + "check checkpoint class clob close clustered coalesce "
                                      + "collate collation column commit completion compute "
                                      + "connect connection constraint constraints constructor "
                                      + "contains containstable continue convert corresponding "
                                      + "count create cross cube current current_date current_path "
                                      + "current_role current_time current_timestamp current_user "
                                      + "cursor cycle data database date day dbcc deallocate dec "
                                      + "decimal declare default deferrable deferred delete deny "
                                      + "depth deref desc describe descriptor destroy destructor "
                                      + "deterministic diagnostics dictionary disconnect disk "
                                      + "distinct distributed domain double drop dummy dump "
                                      + "dynamic each else end end-exec equals errlvl escape "
                                      + "every except exception exec execute exists exit external "
                                      + "extract false fetch file fillfactor first float for "
                                      + "foreign fortran found free freetext freetexttable from "
                                      + "full function general get global go goto grant group "
                                      + "grouping having holdlock host hour identity identity_insert "
                                      + "identitycol if ignore immediate in include index indicator "
                                      + "initialize initially inner inout input insensitive insert "
                                      + "int integer intersect interval into is isolation iterate "
                                      + "join key kill language large last lateral leading left "
                                      + "less level like limit lineno load local localtime localtimestamp "
                                      + "locator lower map match max min minute modifies modify "
                                      + "module month names national natural nchar nclob new next "
                                      + "no nocheck nonclustered none not null nullif number numeric object "
                                      + "octet_length of off offsets old on only open opendatasource "
                                      + "openquery openrowset openxml operation option or order "
                                      + "ordinality out outer output over overlaps pad parameter "
                                      + "parameters partial pascal path percent plan position "
                                      + "postfix precision prefix preorder prepare preserve "
                                      + "primary print prior privileges proc procedure "
                                      + "public raiserror read reads readtext real reconfigure "
                                      + "recursive ref references referencing relative replication "
                                      + "restore restrict result return returns revoke right role "
                                      + "rollback rollup routine row rowcount rowguidcol rows rule "
                                      + "save savepoint schema scope scroll search second section "
                                      + "select sequence session session_user set sets setuser "
                                      + "shutdown size smallint some space specific specifictype "
                                      + "sql sqlca sqlcode sqlerror sqlexception sqlstate sqlwarning "
                                      + "start state statement static statistics structure substring "
                                      + "sum system_user table temporary terminate textsize than then "
                                      + "time timestamp timezone_hour timezone_minute to top trailing "
                                      + "tran transaction translate translation treat trigger trim "
                                      + "true truncate tsequal under union unique unknown unnest "
                                      + "update updatetext upper usage use user using value values "
                                      + "varchar varchar2 variable varying view waitfor when whenever where "
                                      + "while with without work write writetext year zone";

        private string Preprocessors = 
                                             @"@@CONNECTIONS @@CPU_BUSY @@CURSOR_ROWS @@DATEFIRST "
                                             + "@@DBTS @@ERROR @@FETCH_STATUS @@IDENTITY @@IDLE "
                                             + "@@IO_BUSY @@LANGID @@LANGUAGE @@LOCK_TIMEOUT "
                                             + "@@MAX_CONNECTIONS @@MAX_PRECISION @@NESTLEVEL @@OPTIONS "
                                             + "@@PACK_RECEIVED @@PACK_SENT @@PACKET_ERRORS @@PROCID "
                                             + "@@REMSERVER @@ROWCOUNT @@SERVERNAME @@SERVICENAME @@SPID "
                                             + "@@TEXTSIZE @@TIMETICKS @@TOTAL_ERRORS @@TOTAL_READ "
                                             + "@@TOTAL_WRITE @@TRANCOUNT @@VERSION";

        /// <summary>
        /// Plugin name
        /// </summary>
        public string Name
        {
            get
            {
                return "Sourcecode2HTML Plugin for *.sql (Structured Query Language)";
            }
        }
        /// <summary>
        /// Plugin version
        /// </summary>
        public string Version
        {
            get
            {
                return "0.1";
            }
        }
        /// <summary>
        /// Author
        /// </summary>
        public string Author
        {
            get
            {
                return "Евгений Кукло, Евгений Казбан, Власов Иван";
            }
        }
        /// <summary>
        /// Language name
        /// </summary>
        public string LanguageName
        {
            get
            {
                return "SQL (Structured Query Language)";
            }
        }

        /// <summary>
        /// Extension
        /// </summary>
        public string Extension
        {
            get
            {
                return "sql";
            }
        }

        /// <summary>
        /// Converts source code to HTML string
        /// </summary>
        /// <param name="input">Input string</param>
        /// <param name="linenumbers">Draw line numbers</param>
        /// <returns>Returns HTML string</returns>
        public string HighlightKeywords(string input, bool linenumbers)
        {
            bool CaseSensitive = false;
            string CommentRegEx = @"(?:--\s).*?(?=\r|\n)";
            string StringRegEx = @"''|'.*?'";
            Regex r;
            Regex codeRegex;
            r = new Regex(@"\w+|-\w+|#\w+|@@\w+|#(?:\\(?:s|w)(?:\*|\+)?\w+)+|@\\w\*+");
            string regKeyword = r.Replace(Keywords, @"(?<=^|\W)$0(?=\W)");
            string regPreproc = r.Replace(Preprocessors, @"(?<=^|\s)$0(?=\s|$)");
            r = new Regex(@" +");
            regKeyword = r.Replace(regKeyword, @"|");
            regPreproc = r.Replace(regPreproc, @"|");

            if (regPreproc.Length == 0)
                regPreproc = "(?!.*)_{37}(?<!.*)";

            StringBuilder regAll = new StringBuilder();
            regAll.Append("(");
            regAll.Append(CommentRegEx);
            regAll.Append(")|(");
            regAll.Append(StringRegEx);
            if (regPreproc.Length > 0)
            {
                regAll.Append(")|(");
                regAll.Append(regPreproc);
            }
            regAll.Append(")|(");
            regAll.Append(regKeyword);
            regAll.Append(")");


            RegexOptions caseInsensitive = CaseSensitive ? 0 : RegexOptions.IgnoreCase;
            codeRegex = new Regex(regAll.ToString(), RegexOptions.Singleline | caseInsensitive);

            StringBuilder sb = new StringBuilder(input);
            sb.Replace("&", "&amp;");
            sb.Replace("<", "&lt;");
            sb.Replace(">", "&gt;");
            sb.Replace("\t", string.Empty.PadRight(4));
            input = codeRegex.Replace(sb.ToString(), new MatchEvaluator(this.MatchEval));
            sb = new StringBuilder();

            sb.Append("<div class=\"csharpcode\">\n");
            StringReader reader = new StringReader(input);
            int i = 0;
            string spaces = "    ";
            int order;
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                i++;
                if ((i % 2) == 1)
                {
                    sb.Append("<pre class=\"alt\">");
                }
                else
                {
                    sb.Append("<pre>");
                }
                if (linenumbers)
                {
                    order = (int)Math.Log10(i);
                    sb.Append("<span class=\"lnum\">"
                        + spaces.Substring(0, 3 - order) + i.ToString()
                        + ":  </span>");
                }
                if (line.Length == 0)
                    sb.Append("&nbsp;");
                else
                    sb.Append(line);
                sb.Append("</pre>\n");
            }
            reader.Close();
            sb.Append("</div>");

            return sb.ToString();
        }


        /// <summary>
        /// Check matches and convert to HTML tags
        /// </summary>
        /// <param name="match">Match</param>
        /// <returns>Converted string</returns>
        protected string MatchEval(Match match)
        {
            if (match.Groups[1].Success)
            {
                StringReader reader = new StringReader(match.ToString());
                string line;
                StringBuilder sb = new StringBuilder();
                while ((line = reader.ReadLine()) != null)
                {
                    if (sb.Length > 0)
                    {
                        sb.Append("\n");
                    }
                    sb.Append("<span class=\"rem\">");
                    sb.Append(line);
                    sb.Append("</span>");
                }
                return sb.ToString();
            }
            if (match.Groups[2].Success)
            {
                return "<span class=\"str\">" + match.ToString() + "</span>";
            }
            if (match.Groups[3].Success)
            {
                return "<span class=\"preproc\">" + match.ToString() + "</span>";
            }
            if (match.Groups[4].Success)
            {
                return "<span class=\"kwrd\">" + match.ToString() + "</span>";
            }
            System.Diagnostics.Debug.Assert(false, "None of the above!");
            return "";
        }
    }
}
